﻿using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Engine.Terrain
{
    public class HeightMapManager
    {
        #region Public Methods

        public HeightMapTerrain GenerateHeightMapFromFile(Game game, HeightMapParameters pars,string heightMapPath)
        {
            // Generate terrain mesh
            var terrain = GenerateTerrainMesh(game, pars,heightMapPath);
            return terrain;
        }
        public HeightMapTerrain LoadHeightMapFromFile(string heightMapPath)
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            using (Stream stream = File.Open(heightMapPath, FileMode.Open))
            {
                HeightMapTerrain terrain = (HeightMapTerrain) binaryFormatter.Deserialize(stream);
                return terrain;
            }
        }

        #endregion

        #region Private Methods

        private HeightMapTerrain GenerateTerrainMesh(Game game, HeightMapParameters pars,string heightMapPath)
        {
            Texture2D heightMapTexture;
            using(Stream stream=File.Open(heightMapPath,FileMode.Open))
            {
                heightMapTexture=Texture2D.FromStream(game.GraphicsDevice, stream);
            }                        
            HeightMapTerrain terrain = new HeightMapTerrain( pars, heightMapTexture.Width, heightMapTexture.Height);

            FillTerrainWithIndices(terrain);
            FillTerrainVerticesAndHeights(terrain, heightMapTexture);
            FillTerrainWithNormals(terrain);

            return terrain;
        }

        private void FillTerrainWithIndices(HeightMapTerrain terrain)
        {
            int numTriangles = (terrain.VertexCountX - 1)*(terrain.VertexCountZ - 1)*2;
            int numIndices = numTriangles*3;
            List<int> indices = new List<int>(numIndices);

            for (int i = 0; i < (terrain.VertexCountZ - 1); i++)
            {
                for (int j = 0; j < (terrain.VertexCountX - 1); j++)
                {                    
                    int index = j + i*terrain.VertexCountZ;
                    indices.Add(index);                 
                    indices.Add(index + 1);
                    indices.Add(index + terrain.VertexCountX + 1);
                    indices.Add(index + terrain.VertexCountX + 1);
                    indices.Add(index + terrain.VertexCountX);
                    indices.Add(index);
                }
            }
            terrain.Indices = indices.ToArray();
        }
        private void FillTerrainVerticesAndHeights(HeightMapTerrain terrain, Texture2D heightMap)
        {
            float halfTerrainWidth = (terrain.VertexCountX - 1)*terrain.Parameters.BlockSize*0.5f;
            float halfTerrainDepth = (terrain.VertexCountZ - 1)*terrain.Parameters.BlockSize*0.5f;
            terrain.Heights = new float[terrain.VertexCountX,terrain.VertexCountZ];

            Color[] colors = new Color[terrain.VertexCountX*terrain.VertexCountZ];
            heightMap.GetData(colors);
            int vertexCount = 0;
            VertexPositionTexture2Normal[] vertices =
                new VertexPositionTexture2Normal[terrain.VertexCountX*terrain.VertexCountZ];

            int y = 0;

            for (float i = -halfTerrainDepth; i <= halfTerrainDepth; i += terrain.Parameters.BlockSize)
            {
                int x = 0;
                for (float j = -halfTerrainWidth; j <= halfTerrainWidth; j += terrain.Parameters.BlockSize)
                {

                    vertices[vertexCount].Position = new Vector3(j,
                                                                 (colors[vertexCount].R/255f)*
                                                                 terrain.Parameters.HeightScale, i);
                    terrain.Heights[x, y] = vertices[vertexCount].Position.Y;

                    x++;

                    vertices[vertexCount].Texture0 = new Vector2(x, y)*terrain.Parameters.TextureScale;

                    vertexCount++;
                }
                y++;
            }

            terrain.Vertices = vertices;
        }

        private void FillTerrainWithNormals(HeightMapTerrain terrain)
        {
            for (int i = 0; i < terrain.Indices.Length; i += 3)
            {
                Vector3 v1 = terrain.Vertices[terrain.Indices[i]].Position;
                Vector3 v2 = terrain.Vertices[terrain.Indices[i + 1]].Position;
                Vector3 v3 = terrain.Vertices[terrain.Indices[i + 2]].Position;

                Vector3 vu = v3 - v1;
                Vector3 vt = v2 - v1;
                Vector3 normal = Vector3.Cross(vu, vt);
              
                normal.Normalize();

                terrain.Vertices[terrain.Indices[i]].Normal = normal;
                terrain.Vertices[terrain.Indices[i + 1]].Normal = normal;
                terrain.Vertices[terrain.Indices[i + 2]].Normal = normal;
            }          
        }

        #endregion
    }
}
